Skip to content

一文搞懂 OAuth 2.0

OAuth2.0 是一种网络授权机制,给第三方应用授权以获取用户的数据。网络授权的目的,是为API的访问授权提供一个统一开放的标准。

现实案例

你已经高中毕业很多年了,想返校看下当年的老师,看下当年自己生活学习的地方。但是你的高中是个封闭式的学校,进入是需要认证的,作为毕业生的你现在已无权进入,你只能联系你当年的老师,让老师与门卫打声招呼,授权让你进去。

授权机制的设置

  1. 门口增加屏幕,可以选择给你授权的人以及“获取授权”按钮,你选择老师并按下这个按钮。
  2. 老师的手机上弹出“有人想要获取授权”。
  3. 老师点击同意。
  4. 看到同意的消息后,门卫给了你一张卡片,上面有随机生成的授权码(令牌)以及有效期“5小时”。
  5. 你在屏幕上输入授权码,门自动打开,你可以进入学校了。
  6. 过了5小时后,你还没有出来,保安根据卡片定位找到你,并将你驱逐出去,授权码失效。

img

OAuth 2.0 的四种方式

一、授权码(授权码模式)

第三方应用先申请一个授权码,然后再用该码获取令牌。

这种方式是最常用的流程,安全性也最高,它适用于那些有后端的Web应用。授权码通过前端传送,令牌则是储存在后端,而且所有与资源服务器的通信都在后端完成。这样的前后端分离,可以避免令牌泄漏。

第一步,A 网站提供一个链接,用户点击后就会跳转到 B 网站。
bash
https://b.com/oauth/authorize?
    response_type=code&
    client_id=CLIENT_ID&
    redirect_uri=CALLBACK_URL&
    scope=read
  • response_type:授权类型,必选项,此处的值固定为“code”。
  • client_id:客户端ID,必选项。
  • redirect_uri:重定向URI ,可选项。
  • scope:权限范围,可选项。
  • state:客户端的当前状态,可选项。但是为了预防CSRF攻击,一定要设定此值并进行严格检查,认证服务器会原封不动地返回这个值。
第二步,跳转到 B 网站后,会要求用户登录,然后询问是否同意授权。用户同意后, B 网站就会跳回redirect_uri参数指定的网址。
bash
https://a.com/callback?
    code=AUTHORIZATION_CODE
  • code:授权码,必选项。该码的有效期通常为10分钟,客户端只能使用一次。
  • state:如果客户端的请求中包含这个参数,则认证服务器的回应也必须包含这个参数,且值相同。
第三步,A 网站拿到授权码后,就可以向 B 网站请求令牌,请求必须是后端发起。
bash
https://b.com/oauth/token?
    client_id=CLIENT_ID&
    client_secret=CLIENT_SECRET&
    grant_type=authorization_code&
    code=AUTHORIZATION_CODE&
    redirect_uri=CALLBACK_URL
  • client_id:客户端ID,必选项。
  • client_secret:用来让 B 确认 A 的身份,必选项。该参数是保密的,因此只能在后端发请求。
  • grant_type:授权模式,必选项。此处的值固定为“authorization_code”。
  • code:上一步获得的授权码,必选项。
  • redirect_uri:重定向URI,必选项。必须与第一步中的该参数值保持一致。
第四步,B 网站收到请求以后,就会颁发令牌。即向redirect_uri指定的网址,发送一段 JSON 数据。

HTTP头信息中一定要明确指明不得缓存,不得缓存是非常重要的配置项。

json
{
    "access_token":"ACCESS_TOKEN",
    "token_type":"bearer",
    "expires_in":2592000,
    "refresh_token":"REFRESH_TOKEN",
    "scope":"read",
    "uid":100101,
    "info":{}
}
  • access_token:访问令牌,必选项。
  • token_type:令牌类型,该值大小写不敏感,必选项。可以是bearer类型或mac类型。
  • expires_in:令牌过期时间,单位为秒,可选项。若无此参数,则必须以其它方式设置过期时间。
  • refresh_token:更新令牌,用来获取下一次的访问令牌,可选项。
  • scope:权限范围,如果与客户端申请的范围一致,此项可省略。
  • 其它参数
二、隐藏式(简化授权模式)

在纯前端的web应用场景下,就可以使用该模式。因为没有授权码这个步骤,所以称为隐藏式(隐藏了授权码)。

第一步,A 网站提供一个链接,用户点击后就会跳转到 B 网站。
bash
https://b.com/oauth/authorize?
    response_type=token&
    client_id=CLIENT_ID&
    redirect_uri=CALLBACK_URL&
    scope=read
  • response_type:授权类型,值固定为“token”,必选项。
  • client_id:客户端ID,必选项。
  • redirect_uri:重定向URI,可选项。
  • scope:权限范围,可选项。
  • state:客户端的当前状态,可选项。但是为了预防CSRF攻击,一定要设定此值并进行严格检查,认证服务器会原封不动地返回这个值。
第二步,跳转到 B 网站,会要求用户登录。用户登录同意后,B 网站就会跳回redirect_uri参数指定的网址,并且把令牌作为 URL 参数,传给 A 网站。
bash
https://a.com/callback#
    token=ACCESS_TOKEN&
    token_type=TOKEN_TYPE&
    expires_in=3600&
    scope=read
  • access_token:访问令牌,必选项。
  • token_type:令牌类型,该值大小写不敏感,必选项。
  • expires_in:令牌过期时间,单位为秒,可选项。若无此参数,则必须以其它方式设置过期时间。
  • scope:权限范围,如果与客户端申请的范围一致,此项可省略。
  • state:如果客户端的请求中包含这个参数,则认证服务器的回应也必须包含这个参数,且值相同。

需要注意的是,该模式是以锚点而不是查询字符串的方式返回令牌的,这是因为OAuth2.0允许跳转网址是 HTTP 协议,因此存在"中间人攻击"的风险。浏览器跳转时,锚点不会发到服务器,可以减少泄漏令牌的风险。

另外,把令牌直接传给前端是很不安全的。因此该模式只能用于一些对安全要求不高的场景,并且令牌的有效期必须非常短,通常只在会话期间有效,关闭浏览器,令牌就会失效。

三、密码式(密码授权模式)

前后端分离的单页应用一般会采用这种授权模式。

第一步,A 网站要求用户提供 B 网站的用户名和密码。然后 A 直接向 B 请求令牌。
bash
https://oauth.b.com/token?
    grant_type=password&
    username=USERNAME&
    password=PASSWORD&
    scope=read
  • grant_type:授权类型,此处的值固定为“password”,必选项。
  • username:用户名,必选项。
  • password:密码,必选项。
  • scope:权限范围,可选项。
第二步,B 网站验证身份通过后,返回令牌。
json
{
    "access_token": "ACCESS_TOKEN",
    "token_type": "bearer",
    "expires_in": 3600,
    "refresh_token": "REFRESH_TOKEN"
}
四、凭证式(客户端模式)

客户端以自己的名义,而不是以用户的名义,向"服务提供商"进行认证。适用于没有前端的命令行应用,即在命令行下请求令牌。

第一步,A 应用在命令行向 B 发出请求。
bash
https://oauth.b.com/token?
    grant_type=client_credentials&
    client_id=CLIENT_ID&
    client_secret=CLIENT_SECRET
  • grant_type:授权类型,此处的值固定为“client_credentials”,表示采用凭证式。
  • client_id:客户端ID。
  • client_secret:客户端secret。
  • scope:权限范围。
第二步,B 网站验证通过以后,直接返回令牌。

更新令牌

B 网站颁发令牌的时候,会包含两个字段,一个是access_token,还有一个是refresh_token。令牌到期前,用户可以使用refresh_token发出一个请求,去更新令牌。

refresh_token只能刷新一次access_token, 每次刷新之后会返回一个新的access_token和access_token。

bash
https://b.com/oauth/token?
    grant_type=refresh_token&
    client_id=CLIENT_ID&
    client_secret=CLIENT_SECRET&
    refresh_token=REFRESH_TOKEN
  • grant_type:授权类型,此处的值固定为“refresh_token”,表示要求更新令牌。
  • client_id:客户端ID。
  • client_secret:客户端secret。
  • refresh_token:用于更新令牌的令牌。